package com.dk.frame;

import com.dk.common.Constants;
import com.dk.common.GlobalVariable;
import com.dk.util.GBCUtil;
import com.dk.util.QRCodeUtil;
import lombok.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import sun.awt.OSInfo;

import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.Document;
import java.awt.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetAdapter;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.security.AccessController;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.function.Supplier;

import static java.awt.GridBagConstraints.*;


/**
 * 主页面
 *
 * @author: A11181121050450
 * @date: 2020-02-28 16:08
 */
@Slf4j
public class MainFrame extends JFrame {
    private JFrame self = this;
    private CloseFunction closeFunction = null;

    public MainFrame(CloseFunction closeFunction) {
        try {
            log.debug("初始化窗体");
            // 关闭窗口后要调用的
            this.closeFunction = closeFunction;
            //初始化主窗体
//            UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            setLayout(new GridBagLayout());
            setBounds(300, 200, 750, 500);
            setTitle("文件传输");
            setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            // 关闭窗口操作
            addWindowListener(new WindowAdapter() {
                @Override
                public void windowClosing(WindowEvent e) {
                    //调用上面关闭窗口后需要执行的方法
                    closeFunction.close();
                    System.exit(0);
                }
            });

            PanelContent<JPanel> mainPanelContent = mainPanel();
            JTabbedPane jTabbedPane = mainPanelContent.getComponent(Constants.TAB_PANE);

            // 下载tab
            TabPanel downloadTabPanel = new TabPanel(mainPanelContent,
                    new TabInfo("请选择文件:", () -> Constants.DOWNLOAD_URI,
                            (s) -> GlobalVariable.downloadFilePath = s)
                            .createFileChooser(JFileChooser.FILES_ONLY, "请选择文件"));
            PanelContent<JPanel> downloadPanelContent = downloadTabPanel.createPanel();
            JPanel panel = downloadPanelContent.getPanel();
            new DropTarget(panel, DnDConstants.ACTION_COPY_OR_MOVE, new DropTargetAdapter() {
                @Override
                public void drop(DropTargetDropEvent dtde) {
                    boolean isAccept = false;
                    try {
                        /*
                         * 1. 文件: 判断拖拽目标是否支持文件列表数据（即拖拽的是否是文件或文件夹, 支持同时拖拽多个）
                         */
                        if (dtde.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
                            // 接收拖拽目标数据
                            dtde.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
                            isAccept = true;

                            // 以文件集合的形式获取数据
                            List<File> files = null;

                            files = (List<File>) dtde.getTransferable().getTransferData(DataFlavor.javaFileListFlavor);


                            // 把文件路径输出到文本区域
                            if (!CollectionUtils.isEmpty(files)) {
                                File file = files.get(files.size() - 1);
                                JTextField jTextField = downloadPanelContent.getComponent("jTextField");
                                jTextField.setText(file.getAbsolutePath());
                            }
                        }
                    } catch (UnsupportedFlavorException e) {
                        log.error(e.getMessage(), e);
                    } catch (IOException e) {
                        log.error(e.getMessage(), e);
                    } finally {
                        // 如果此次拖拽的数据是被接受的, 则必须设置拖拽完成（否则可能会看到拖拽目标返回原位置, 造成视觉上以为是不支持拖拽的错误效果）
                        if (isAccept) {
                            dtde.dropComplete(true);
                        }
                    }
                }
            }, true);

            // 上传tab
            TabInfo uploadTableInfo = new TabInfo("请选择文件夹", () -> Constants.UPLOAD_PAGE_URI,
                    (s) -> {
                        GlobalVariable.uploadFolderPath = s;
                        Constants.PROPERTIES.setProperty(Constants.UPLOAD_DIC_PATH, s);
                    }).createFileChooser(JFileChooser.DIRECTORIES_ONLY, "请选择文件夹");
            TabPanel uploadTabPanel = new TabPanel(mainPanelContent, uploadTableInfo) {
                // 用来给上传tab后面增加一个打开文件夹的功能
                @Override
                public JComponent filterAddComponent(JPanel jPanel, JComponent jComponent, GridBagConstraints arg) {
                    if (arg.gridx == 1 && arg.gridy == 0) {
                        arg.weightx = 0;
                    }
                    jComponent = super.filterAddComponent(jPanel, jComponent, arg);
                    if (arg.gridx == 2 && arg.gridy == 0) {
                        arg.gridx++;
                        arg.weightx = 1;
                        JButton jButton = new JButton("打开文件夹");
                        jButton.addActionListener(e -> open());
                        jPanel.add(jButton, arg);
                    }
                    return jComponent;
                }

                ;

                /**
                 * 用来打开文件夹
                 */
                private void open() {
                    String str = GlobalVariable.uploadFilePath;
                    try {
                        if (!StringUtils.isEmpty(str) && new File(str).exists()) {
                            OSInfo.OSType osType = AccessController.doPrivileged(OSInfo.getOSTypeAction());
                            if (osType == OSInfo.OSType.WINDOWS) {
                                Runtime.getRuntime().exec("explorer /select, " + str);
                            }else if (osType == OSInfo.OSType.MACOSX){
                                Runtime.getRuntime().exec("open -R "+str);
                            }else if (osType == OSInfo.OSType.LINUX){
                                // ubuntu系统
                                if(new File("/etc/lsb-release").exists()){
                                    Runtime.getRuntime().exec("explorer /select, " + str);
                                }
                                Desktop.getDesktop().open(new File(str).getParentFile());
                            }else {
                                Desktop.getDesktop().open(new File(str).getParentFile());
                            }
                        }else{
                            if(!StringUtils.isEmpty(GlobalVariable.uploadFolderPath) && new File(GlobalVariable.uploadFolderPath).exists()) {
                                Desktop.getDesktop().open(new File(GlobalVariable.uploadFolderPath));
                            }
                        }
                    } catch (IOException e) {
                        log.error(e.getMessage(), e);
                    }
                }

                ;
            };
            PanelContent<JPanel> uploadPanelContent = uploadTabPanel.createPanel();

            // 向主页面添加tab
            jTabbedPane.addTab("下载", panel);
            jTabbedPane.addTab("上传", uploadPanelContent.getPanel());
            //用来初始化信息
            synchronized (GlobalVariable.initFinish) {
                while (!GlobalVariable.initFinish) {
                    GlobalVariable.initFinish.wait();
                }
                // 把配置文件中的数据获取出来
                String ipAddr = Constants.PROPERTIES.getProperty(Constants.IP_ADDR);
                if (!StringUtils.isEmpty(ipAddr)) {
                    JComboBox jComboBox = mainPanelContent.getComponent(Constants.IP_LIST_COMBO_BOX);
                    jComboBox.setSelectedItem(ipAddr);
                }
                // 初始化上传的文件夹地址
                GlobalVariable.uploadFolderPath = Constants.PROPERTIES.getProperty(Constants.UPLOAD_DIC_PATH);
                if (!StringUtils.isEmpty(GlobalVariable.uploadFolderPath)) {
                    JTextField jTextField = uploadPanelContent.getComponent("jTextField");
                    jTextField.setText(GlobalVariable.uploadFolderPath);
                }
                // 开始显示
                setVisible(true);
            }
        } catch (Throwable e) {
            log.error(e.getMessage(), e);
        }
    }

    /**
     * 主窗口
     *
     * @return
     */
    private PanelContent<JPanel> mainPanel() {
        // 第一行
        GridBagConstraints r1 = GBCUtil.create().setFill(NONE).setGridy(0).setGridwidth(1).build();
        JLabel jLabel1 = new JLabel("请选择网络地址:");
        add(jLabel1, r1);
        // 下拉框
        r1.anchor = WEST;
        JComboBox<String> jComboBox = new JComboBox<>();
        getIpList().stream().forEach(o -> jComboBox.addItem(o));
        // 保存上次地址
        jComboBox.addItemListener(e -> Constants.PROPERTIES.setProperty(Constants.IP_ADDR, jComboBox.getSelectedItem().toString()));
        add(jComboBox, r1);

        // 第二行
        GridBagConstraints r2 = GBCUtil.create().setAnchor(NORTHWEST)
                .setFill(BOTH).setWeightx(1).setWeighty(1).setGridy(1).setGridwidth(2).build();
        JTabbedPane jTabbedPane = new JTabbedPane();
        add(jTabbedPane, r2);

        PanelContent<JPanel> result = new PanelContent<>();
        result.setComponent(Constants.TAB_PANE, jTabbedPane);
        result.setComponent(Constants.IP_LIST_COMBO_BOX, jComboBox);

        return result;
    }

    /**
     * 获取本机ip地址
     *
     * @return
     */
    private List<String> getIpList() {
        try {
            List<String> result = new ArrayList<>(5);
            Enumeration<NetworkInterface> allNetInterfaces = NetworkInterface.getNetworkInterfaces();
            InetAddress ip = null;
            while (allNetInterfaces.hasMoreElements()) {
                NetworkInterface netInterface = allNetInterfaces.nextElement();
                if (netInterface.isLoopback() || netInterface.isVirtual() || !netInterface.isUp()) {
                    continue;
                } else {
                    Enumeration<InetAddress> addresses = netInterface.getInetAddresses();
                    while (addresses.hasMoreElements()) {
                        ip = addresses.nextElement();
                        if (ip != null && ip instanceof Inet4Address) {
                            result.add(ip.getHostAddress());
                        }
                    }
                }
            }
            return result;
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    /**
     * 生成QR码
     *
     * @param ipAddr ip地址
     * @return
     * @throws Exception
     */
    private ImageIcon createQR(String ipAddr, String suffixPath) throws Exception {
        log.debug("filePath:" + GlobalVariable.downloadFilePath);
        String content = Constants.PROTOCOL + "://" + ipAddr + ":" + Constants.PORT + suffixPath;
        BufferedImage img = QRCodeUtil.encode(content, null, false);
        return new ImageIcon(img);
    }

    /**
     * 关闭窗口后执行的方法
     */
    @FunctionalInterface
    public interface CloseFunction {
        void close();
    }

    /**
     * 文本框内容发生变化时调用的接口
     */
    @FunctionalInterface
    public interface ContentChange extends DocumentListener {
        // 操作类型为新增
        int INSERT_OPERATING = 1;
        // 操作类型为修改
        int CHANGE_OPERATING = 2;
        // 操作类型为删除，按退格键就能触发
        int REMOVE_OPERATING = 3;

        void change(DocumentEvent e, int operating);

        @Override
        default void insertUpdate(DocumentEvent e) {
            change(e, INSERT_OPERATING);
        }

        @Override
        default void removeUpdate(DocumentEvent e) {
            change(e, REMOVE_OPERATING);
        }

        @Override
        default void changedUpdate(DocumentEvent e) {
            change(e, CHANGE_OPERATING);
        }
    }

    /**
     * 用来存放组件的
     *
     * @param <T>
     */
    public class PanelContent<T extends Component> {
        @Getter
        @Setter
        private T panel;
        private Map<String, Component> component = new ConcurrentHashMap<>();

        public <V extends Component> V getComponent(String name) {
            Component component = this.component.get(name);
            return (V) component;
        }

        public <V extends Component> V setComponent(String name, V component) {
            return (V) this.component.put(name, component);
        }
    }

    /**
     * 存放选择文件的那个条信息
     */
    @RequiredArgsConstructor
    @NoArgsConstructor
    private class TabInfo {
        @NonNull
        public String title;
        @NonNull
        // 生成二维码地址
        public Supplier<String> urlPath;
        @NonNull
        // 用来处理选择文件或文件夹后的逻辑处理
        public Consumer<String> filePath;
        // 点击选择文件夹按钮后的操作
        public Supplier<JFileChooser> jFileChooser;

        public TabInfo createFileChooser(int status, String title) {
            jFileChooser = () -> {
                JFileChooser jfc = new JFileChooser();
                // 只选择文件
                jfc.setFileSelectionMode(status);
                // 以dialog方式弹出，标题为选择
                jfc.showDialog(null, title);

                return jfc;
            };
            return this;
        }
    }

    /**
     * 每个tab中的内容
     */
    @AllArgsConstructor
    @NoArgsConstructor
    @Data
    private class TabPanel extends JPanel {
        private PanelContent<JPanel> mainPanel;
        private TabInfo tabInfo;

        public final PanelContent<JPanel> createPanel() {
            JPanel jPanel = new JPanel(new GridBagLayout());

            // 第二行的二维码外框
            JLabel imageLabel = new JLabel();
            // 第一行文本框
            JTextField jTextField = new JFormattedTextField();

            //下拉框的内容
            JComboBox jComboBox = mainPanel.getComponent(Constants.IP_LIST_COMBO_BOX);
            jComboBox.addItemListener(e -> {
                try {
                    // 获取文件地址
                    String text = jTextField.getText();
                    if (!StringUtils.isEmpty(text)) {
                        text = StringUtils.trimLeadingCharacter(text, '"');
                        text = StringUtils.trimTrailingCharacter(text, '"');
                        tabInfo.filePath.accept(text);
                    }
                    // 创建二维码
                    imageLabel.setIcon(createQR(jComboBox.getSelectedItem().toString(), tabInfo.urlPath.get()));
                } catch (Exception e1) {
                    log.error(e1.getMessage(), e1);
                    JOptionPane.showMessageDialog(self, "二维码创建失败，请重新选择", "提示", JOptionPane.QUESTION_MESSAGE);
                }
            });

            //第一行内容
            GridBagConstraints r1 = GBCUtil.create().setGridy(0).setGridwidth(1).build();
            // 靠东放置
            r1.anchor = EAST;
            JLabel jLabel2 = new JLabel(tabInfo.title);
            filterAddComponent(jPanel, jLabel2, r1);
            // 监听文本框内容变化
            jTextField.getDocument().addDocumentListener((ContentChange) (e, operating) -> {
                log.debug("operating:" + operating);
                try {
                    //获取输入的文件路径
                    Document document = e.getDocument();
                    String text = document.getText(0, document.getLength());
                    if (!StringUtils.isEmpty(text)) {
                        text = StringUtils.trimLeadingCharacter(text, '"');
                        text = StringUtils.trimTrailingCharacter(text, '"');
                    }
                    tabInfo.filePath.accept(text);
                    // 创建二维码
                    imageLabel.setIcon(createQR(jComboBox.getSelectedItem().toString(), tabInfo.urlPath.get()));
                } catch (Exception e1) {
                    log.error(e1.getMessage(), e1);
                    JOptionPane.showMessageDialog(self, "二维码创建失败，请重新选择", "提示", JOptionPane.QUESTION_MESSAGE);
                }
            });
            // 文本框横向占满
            r1.fill = GridBagConstraints.HORIZONTAL;
            // 文本框随着窗体拖拽横向变大
            r1.weightx = 1;
            filterAddComponent(jPanel, jTextField, r1);
            JButton jButton = new JButton("请选择");
            r1.fill = GridBagConstraints.NONE;
            r1.anchor = WEST;
            // 请选择按钮增加事件，点击后出来选择文件的窗体
            jButton.addActionListener(e -> {
                JFileChooser jfc = tabInfo.jFileChooser.get();
                // 获取选中文件
                File file = jfc.getSelectedFile();
                // 怕没选文件直接取消了
                if (file != null) {
                    String absolutePath = file.getAbsolutePath();
                    jTextField.setText(absolutePath);
                }
            });
            filterAddComponent(jPanel, jButton, r1);

            //第二行内容
            GridBagConstraints r2 = GBCUtil.create().setGridy(1).setGridwidth(1).build();
            // 组件靠东北放置
            r2.anchor = NORTHEAST;
            JLabel jLabel3 = new JLabel("二维码:");
            filterAddComponent(jPanel, jLabel3, r2);
            // 二维码框组件横跨两个位置
            r2.gridwidth = 3;
            // 二维码框不进行填充
            r2.fill = NONE;
            // 二维码框靠西北方向放置
            r2.anchor = NORTHWEST;
            // 二维码框外框横向和纵向拖拽都最窗体变动
            r2.weightx = r2.weighty = 1;
            filterAddComponent(jPanel, imageLabel, r2);

            PanelContent<JPanel> panelContent = new PanelContent<>();
            panelContent.setPanel(jPanel);
            panelContent.setComponent("jTextField", jTextField);
            return panelContent;
        }

        /**
         * 添加组件用的拦截器
         *
         * @param jPanel
         * @param jComponent
         * @param arg
         * @return
         */
        public JComponent filterAddComponent(JPanel jPanel, JComponent jComponent, GridBagConstraints arg) {
            arg.gridx = arg.gridx + 1;
            jPanel.add(jComponent, arg);
            return jComponent;
        }

        ;
    }
}
